
/*
 test w25n01zeig base driver
*/

#include <w25n01gvzeig.h>
#include <w25n01gv_drv.h>

#include <rtthread.h>

#define DBG_TAG "W25N01GV"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

static char log_buf[0x80];

static void check_w25n01gv_SR(void)
{
    w25n01gv_uint8_t status;
    w25n01gv_read_status(W25N01GVZEIG_SR1_ADDR, &status);
    w25n01gvzeig_SR1 *SR1 = (w25n01gvzeig_SR1 *)&status;

    rt_kprintf("\n-------------------------------------------------------\n");
    rt_kprintf("w25n01gvzeig status register: \n");
    rt_sprintf(log_buf, "SR1: [ SRP0 = %d, BP3 = %d, BP2 = %d, BP1 = %d, BP0 = %d, TB = %d, WP-E = %d, SRP1 = %d ]\n",
               SR1->status_register_protect_0,
               SR1->block_protect_3, SR1->block_protect_2, SR1->block_protect_1, SR1->block_protect_0,
               SR1->top_bottom_protect,
               SR1->write_protect_enable,
               SR1->status_register_protect_1);
    rt_kprintf("%s\n", log_buf);

    w25n01gv_read_status(W25N01GVZEIG_SR2_ADDR, &status);
    w25n01gvzeig_SR2 *SR2 = (w25n01gvzeig_SR2 *)&status;
    rt_sprintf(log_buf, "SR2: [ OTP-L = %d, OTP-E = %d, SR1-L = %d, ECC-E = %d, BUF = %d ]\n",
               SR2->otp_lock, SR2->otp_enable, SR2->status_reg1_lock, SR2->ecc_enable, SR2->read_buf_continue_mode);
    rt_kprintf("%s\n", log_buf);

    w25n01gv_read_status(W25N01GVZEIG_SR3_ADDR, &status);
    w25n01gvzeig_SR3 *SR3 = (w25n01gvzeig_SR3 *)&status;
    rt_sprintf(log_buf, "SR3: [ LUT-F = %d, ECC-1 = %d, ECC-0 = %d, P-FAIL = %d, E-FAIL = %d, WEL = %d, BUSY = %d ]\n",
               SR3->bbm_lut_full, SR3->ecc_status_1, SR3->ecc_status_0,
               SR3->program_failure, SR3->erase_failure, SR3->write_enable_latch, SR3->busy);
    rt_kprintf("%s\n", log_buf);
    rt_kprintf("\n-------------------------------------------------------\n");
}

static int w25n01gvzeig_check_parameter_page(void)
{
    w25n01gv_uint8_t *parameter_page_data_buf = rt_malloc(W25N01GV_PARAMETER_PAGE_COPY_SIZE);

    int rslt = w25n01gv_read_otp_page(parameter_page_data_buf, W25N01GV_PARAMETER_PAGE_COPY_SIZE, (enum w25n01gv_otp_page)parameter_page);
    if (rslt != W25N01GVZEIG_SUCCESS) {
        rt_sprintf(log_buf, "read w25n01gvzeig parameter page failed, return code = %d\n", rslt);
        rt_kprintf("%s\n", log_buf);
        rt_free(parameter_page_data_buf);
        return -1;
    }

    rt_kprintf("\n-------------------------------------------------------\n");
    rt_kprintf("read w25n01gvzeig parameter page:\n");
    rt_sprintf(log_buf, "parameter page first four byte: %02x, %02x, %02x, %02x",
               parameter_page_data_buf[0], parameter_page_data_buf[1],
               parameter_page_data_buf[2], parameter_page_data_buf[3]);
    rt_kprintf("%s\n", log_buf);

    if (parameter_page_data_buf[0] != 0x4f ||
        parameter_page_data_buf[1] != 0x4e ||
        parameter_page_data_buf[2] != 0x46 ||
        parameter_page_data_buf[3] != 0x49) {
        rt_kprintf("read w25n01gvzeig parameter page failed\n");
        rt_free(parameter_page_data_buf);

        w25n01gv_write_status(W25N01GVZEIG_SR2_ADDR, W25NGVZEIG_SR2_BIT_WR_OTP_E, 0);
        return -1;
    }

    w25n01gv_uint8_t buf[15] = {0};
    rt_memcpy(buf, parameter_page_data_buf + 32, 12);
    rt_sprintf(log_buf, "manufacture: %s, jedec id: %02x", buf, parameter_page_data_buf[64]);
    rt_kprintf("%s\n", log_buf);

    w25n01gv_uint32_t *data_bytes_per_page  = (w25n01gv_uint32_t *)(parameter_page_data_buf + 80);
    w25n01gv_uint16_t *spare_bytes_per_page = (w25n01gv_uint16_t *)(parameter_page_data_buf + 84);

    rt_sprintf(log_buf, "Number of data bytes per page %d, Number of spare bytes per page %d", *data_bytes_per_page, *spare_bytes_per_page);
    rt_kprintf("%s\n", log_buf);

    w25n01gv_uint32_t *pages_per_block;
    w25n01gv_uint32_t *blocks_per_unit;
    pages_per_block = (w25n01gv_uint32_t *)(parameter_page_data_buf + 92);
    blocks_per_unit = (w25n01gv_uint32_t *)(parameter_page_data_buf + 96);
    rt_sprintf(log_buf, "Number of pages per block %d, Number of blocks per LU %d, Number of LU %d", *pages_per_block, *blocks_per_unit, parameter_page_data_buf[100]);
    rt_kprintf("%s\n", log_buf);

    if (parameter_page_data_buf[102] == 0x01) {
        rt_kprintf("w25n01gvzeig is SLC\n");
    } else if (parameter_page_data_buf[102] == 0x02) {
        rt_kprintf("w25n01gvzeig is MLC\n");
    } else if (parameter_page_data_buf[102] == 0x03) {
        rt_kprintf("w25n01gvzeig is TLC\n");
    } else if (parameter_page_data_buf[102] == 0x04) {
        rt_kprintf("w25n01gvzeig is QLC\n");
    } else {
        rt_kprintf("w25n01gvzeig bit per cell is unknown\n");
    }

    w25n01gv_uint16_t *bad_blocks_max_per_LU;
    w25n01gv_uint16_t *block_endurance;
    bad_blocks_max_per_LU = (w25n01gv_uint16_t *)(parameter_page_data_buf + 103);
    block_endurance       = (w25n01gv_uint16_t *)(parameter_page_data_buf + 105);
    rt_sprintf(log_buf, "maximum bad blocks per LU %d, block endurance %d", *bad_blocks_max_per_LU, *block_endurance);
    rt_kprintf("%s\n", log_buf);
    rt_kprintf("\n-------------------------------------------------------\n");

    rt_free(parameter_page_data_buf);

    return W25N01GVZEIG_SUCCESS;
}

static int w25n01gvzeig_scan_blocks(void)
{
    w25n01gvzeig_deivce_info device_info;
    w25n01gvzeig_fetch_device_info(&device_info);

    /*  A “Bad Block Marker” is a non-FFh data byte stored at Byte 0 of
        Page 0 for each bad block. An additional marker is also stored
        in the first byte of the 64-Byte spare area.
    */
    w25n01gv_uint16_t page_addr;
    w25n01gv_uint8_t byte0_bad_block_mark;
    w25n01gv_uint8_t byte_bad_block_mark_at_spare[2];
    w25n01gv_uint16_t bad_block_cnt = 0;

    rt_kprintf("\n-------------------------------------------------------\n");
    rt_kprintf("start scanning w25n01gvzeig blocks\n");
    for (w25n01gv_uint16_t i = 0; i < device_info.blocks_per_nand; i++) {
        page_addr = (i << W25N01GV_PAGE_ADDR_LEN);

        w25n01gv_paga_data_read(page_addr);
        w25n01gv_read(0, &byte0_bad_block_mark, 1);
        w25n01gv_read(W25N01GV_DATA_BYTES_PER_PAGE, byte_bad_block_mark_at_spare, 2);

        if (byte_bad_block_mark_at_spare[0] != 0xff) {
            /* bad block */
            bad_block_cnt++;
            rt_sprintf(log_buf, "block %d is bad, spare head 2 byte: %02x %02x", i, byte_bad_block_mark_at_spare[0], byte_bad_block_mark_at_spare[1]);
            rt_kprintf("%s\n", log_buf);
        }
    }
    rt_sprintf(log_buf, "total blocks is %d , %d blocks is bad", device_info.blocks_per_nand, bad_block_cnt);
    rt_kprintf("%s\n", log_buf);
    rt_kprintf("\n-------------------------------------------------------\n");
    return W25N01GVZEIG_SUCCESS;
}

static int w25n01gvzeig_read_manufacture_jedec_id(void)
{
    w25n01gv_uint8_t manufacture_id;
    w25n01gv_uint8_t jedec_id[2];
    w25n01gv_jedec_id(&manufacture_id, jedec_id);
    w25n01gv_uint16_t jedec_id_combine = jedec_id[0] << 8 | jedec_id[1];

    rt_kprintf("\n-------------------------------------------------------\n");
    rt_kprintf("start read manufacture and jedec id\n");
    rt_sprintf(log_buf, "manufacture id = %02x, jedec id = %02x\n", manufacture_id, jedec_id_combine);
    rt_kprintf("%s\n", log_buf);
    if (manufacture_id == W25N01GV_MANUFACTURER_ID && jedec_id_combine == W25N01GV_DEVICE_ID) {
        rt_kprintf("w25n01gv_jedec_id success\n");
        rt_kprintf("\n-------------------------------------------------------\n");
    } else {
        rt_kprintf("w25n01gv_jedec_id failed\n");
        rt_kprintf("\n-------------------------------------------------------\n");
        return -1;
    }

    return W25N01GVZEIG_SUCCESS;
}

static int w25n01gvzeig_write_read(w25n01gv_uint16_t page_address, w25n01gv_uint8_t quad)
{
    /* unlock main array */
    w25n01gvzeig_SR1 SR1_unlock_set;
    resolve_SR1_block_protection_bits_field(cover_8000_ffff_64MB, &SR1_unlock_set);
    w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_TB, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));
    w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_BP, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));

    w25n01gv_uint8_t *tx_buf = rt_malloc(W25N01GV_DATA_BYTES_PER_PAGE);
    if (!tx_buf) {
        rt_kprintf("malloc failed\n");
        resolve_SR1_block_protection_bits_field(All, &SR1_unlock_set);
        w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_TB, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));
        w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_BP, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));
        return -1;
    }

    for (w25n01gv_uint16_t i = 0; i < W25N01GV_DATA_BYTES_PER_PAGE; i++) {
        tx_buf[i] = i;
    }

    int rslt = quad ? w25n01gv_quad_program_data_load(0, tx_buf, W25N01GV_DATA_BYTES_PER_PAGE, 0) : w25n01gv_program_data_load(0, tx_buf, W25N01GV_DATA_BYTES_PER_PAGE, 0);

    if (rslt != W25N01GVZEIG_SUCCESS) {
        rt_kprintf("w25n01gv_program_data_load failed, return %d\n", rslt);
        rt_free(tx_buf);
        resolve_SR1_block_protection_bits_field(All, &SR1_unlock_set);
        w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_TB, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));
        w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_BP, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));
        return -1;
    }

    w25n01gv_program_execute(page_address);
    rt_thread_mdelay(100);

    w25n01gv_paga_data_read(page_address);

    w25n01gv_read(0, tx_buf, W25N01GV_DATA_BYTES_PER_PAGE);

    w25n01gv_uint16_t error_bytes = 0;
    for (w25n01gv_uint16_t i = 0; i < W25N01GV_DATA_BYTES_PER_PAGE; i++) {
        if (tx_buf[i] != (w25n01gv_uint8_t)i)
            error_bytes++;
    }
    rt_kprintf("total rw %d bytes, error %d\n", W25N01GV_DATA_BYTES_PER_PAGE, error_bytes);

    rt_free(tx_buf);
    resolve_SR1_block_protection_bits_field(All, &SR1_unlock_set);
    w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_TB, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));
    w25n01gv_write_status(W25N01GVZEIG_SR1_ADDR, W25NGVZEIG_SR1_BIT_WR_BP, *((w25n01gv_uint8_t *)(&SR1_unlock_set)));

    return W25N01GVZEIG_SUCCESS;
}

/* test load program data(02h), program execute(10h), page data read(13h), read data(03h) */
static int w25n01gvzeig_test_02h_10h_13h_03h()
{
    rt_kprintf("start write read block1 page 0, test load program data(02h), program execute(10h), page data read(13h), read data(03h) \n");
    w25n01gv_uint16_t page_address = 01 << W25N01GV_PAGE_ADDR_LEN; /* block 1 page 0 */
    return w25n01gvzeig_write_read(page_address, 0);
}
/* test quad load program data(32h), program execute(10h), page data read(13h), read data(03h) */
static int w25n01gvzeig_test_32h_10h_13h_03h()
{
    rt_kprintf("start write read block1 page 1, test load program data(32h), program execute(10h), page data read(13h), read data(03h) \n");
    w25n01gv_uint16_t page_address = 01 << W25N01GV_PAGE_ADDR_LEN | 1; /* block 1 page 1 */
    return w25n01gvzeig_write_read(page_address, 1);
}

int w25n01gvzeig_driver_test(void)
{
    int rslt;

    /* step1: init */
    board_w25n01gv_init();

    rt_kprintf("first check w25n01gv status register\n");
    check_w25n01gv_SR();

    /* step2: call reset function */
    w25n01gv_reset();
    rt_thread_mdelay(100);
    rt_kprintf("check w25n01gv status register after reset\n");
    check_w25n01gv_SR();

    /* step3: read w25n01gv manufacture id and jedec id */
    rslt = w25n01gvzeig_read_manufacture_jedec_id();
    if (rslt != W25N01GVZEIG_SUCCESS) {
        return rslt;
    }

    /* step4: read parameter page, check and show */
    rslt = w25n01gvzeig_check_parameter_page();
    if (rslt != W25N01GVZEIG_SUCCESS) {
        return rslt;
    }

    /* step5: scan all the blocks and log into the BBM LUT(bad block management look up table) */
    rslt = w25n01gvzeig_scan_blocks();
    if (rslt != W25N01GVZEIG_SUCCESS) {
        return rslt;
    }

    rslt = w25n01gvzeig_test_02h_10h_13h_03h();
    if (rslt != W25N01GVZEIG_SUCCESS) {
        return rslt;
    }

    // rslt = w25n01gvzeig_test_32h_10h_13h_03h();
    // if (rslt != W25N01GVZEIG_SUCCESS) {
    //     return rslt;
    // }

    rt_kprintf("after driver test check w25n01gv status register\n");
    check_w25n01gv_SR();

    return W25N01GVZEIG_SUCCESS;
}

MSH_CMD_EXPORT(w25n01gvzeig_driver_test, w25n01gvzeig_driver_test)